//BeanCipher - Bean Cipher Security Layer
//Copyright (C) 2005 Raul Alfredo Bajales
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package ar.com.rab.beancipher.impl.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.xml.sax.InputSource;

import ar.com.rab.beancipher.impl.configuration.exceptions.ConfigurationException;
import ar.com.rab.beancipher.impl.configuration.model.beanmapping.CFGBeanMapping;
import ar.com.rab.beancipher.impl.configuration.model.registry.CFGRegistry;
import ar.com.rab.beancipher.impl.configuration.validator.ConfigurationValidator;
import ar.com.rab.beancipher.impl.configuration.validator.exceptions.ConfigurationValidatorException;
import ar.com.rab.beancipher.spi.IBeanDescriptor;
import ar.com.rab.beancipher.spi.IBeanMapping;

/**
 * Contains bean mappings, and can re-load the mapping files
 * without shutdown the VM.
 * 
 * @author Bajales Raul
 *
 */
public class Configuration {
    
    private static Log log = LogFactory.getLog(Configuration.class);
    private static final String CASTOR_BEAN_MAPPING = "bean-mapping-castor.xml";
    private static final String CASTOR_REGISTRY = "registry-castor.xml";
    private static final String BEAN_CIPHER_PROPERTIES = "bean-cipher.properties";
    private static final String BEAN_MAPPING_FILE_NAME_KEY = "configuration.bean.mapping.file.name";
    private static final String REGISTRY_FILE_NAME_KEY = "configuration.registry.file.name";
    private static final String VALIDATE_KEY = "configuration.validate.files";
    private Properties properties;
    private IBeanMapping beanMapping;
    
    /**
     *  Constructor
     * @throws ConfigurationException
     */
    public Configuration() throws ConfigurationException {
        super();
        this.properties = new Properties();
    }

    /**
     * Reload's the configuration
     */
    public void reload() throws ConfigurationException {
        try {
            log.debug("trying to load configuration...");
            this.properties.load(new FileInputStream(new File(BEAN_CIPHER_PROPERTIES)));
            CFGBeanMapping cfgBeanMapping = loadCFGBeanMapping(this.properties.getProperty(BEAN_MAPPING_FILE_NAME_KEY));
            CFGRegistry cfgRegistry = loadCFGRegistry(this.properties.getProperty(REGISTRY_FILE_NAME_KEY));
            checkForValidation(cfgBeanMapping, cfgRegistry);
            buildBeanMappingFrom(cfgBeanMapping, cfgRegistry);
        } catch (Exception e) {
            throw new ConfigurationException("Can't load configuration", e);
        }
    }

    /**
     * @param cfgBeanMapping
     * @param cfgRegistry
     * @throws Exception
     */
    private void buildBeanMappingFrom(CFGBeanMapping cfgBeanMapping, CFGRegistry cfgRegistry) throws Exception {
    	BeanMappingBuilder builder = new BeanMappingBuilder(cfgBeanMapping, cfgRegistry);
    	builder.build();
    	this.beanMapping = builder.get();
    }

    /**
     * @param property
     * @return CFGRegistry
     * @throws MappingException
     * @throws IOException
     * @throws FileNotFoundException
     * @throws ValidationException
     * @throws MarshalException
     */
    private CFGRegistry loadCFGRegistry(String fileName) throws IOException, MappingException, MarshalException, ValidationException, FileNotFoundException {
    	Mapping mapping = new Mapping();
    	mapping.loadMapping(new InputSource(this.getClass().getResourceAsStream(CASTOR_REGISTRY)));
    	Unmarshaller unmarshaller = new Unmarshaller(mapping);
        return (CFGRegistry) unmarshaller.unmarshal(new InputSource(new FileReader(fileName)));
    }

    /**
     * @param fileName
     * @return CFGBeanMapping
     * @throws MappingException
     * @throws IOException
     * @throws FileNotFoundException
     * @throws ValidationException
     * @throws MarshalException
     */
    private CFGBeanMapping loadCFGBeanMapping(String fileName) throws IOException, MappingException, MarshalException, ValidationException, FileNotFoundException {
    	Mapping mapping = new Mapping();
    	mapping.loadMapping(new InputSource(this.getClass().getResourceAsStream(CASTOR_BEAN_MAPPING)));
    	Unmarshaller unmarshaller = new Unmarshaller(mapping);
        return (CFGBeanMapping) unmarshaller.unmarshal(new InputSource(new FileReader(fileName)));
    }

    /**
     * @param cfgBeanMapping
     * @param cfgRegistry
     * @throws ConfigurationValidatorException
     */
    private void checkForValidation(CFGBeanMapping cfgBeanMapping, CFGRegistry cfgRegistry) throws ConfigurationValidatorException {
        String validate = this.properties.getProperty(VALIDATE_KEY);
        if (validate == null) return;
        if (validate.trim().equalsIgnoreCase(Boolean.TRUE.toString())) 
            ConfigurationValidator.validate(cfgBeanMapping, cfgRegistry);
    }

    /**
     * @param beanClassName
     * @return BeanMapping.BeanDescriptor
     */
    public IBeanDescriptor getDescriptorFor(String beanClassName) {
        return this.beanMapping.getDescriptorFor(beanClassName);
    }

    /**
     * @return Configuration
     * @throws ConfigurationException
     */
    public static Configuration build() throws ConfigurationException {
        Configuration configuration = new Configuration();
        configuration.reload();
        return configuration;
    }
}
